package modelo.reglas.engine;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import modelo.Cupon;
import modelo.Oferta;
import modelo.OrdenDeCompra;
import modelo.Producto;
import modelo.query.InvalidQueryException;
import modelo.reglas.composite.inner.GroupSpecification;
import util.ProductUtils;
import util.QueryUtils;

import com.google.common.collect.Collections2;

/*
 * Para una venta cualquiera obtengo la mejor aplicacion de ofertas de forma de beneficiar al consumidor
 */
public class BestSell {

	private static String MAX_DISCOUNT_PERCENT = "40";

	/*
	 * Devuelve el mejor set de ofertas en el orden que lo promueve
	 */

	public static Oferta calculateBestSellOffert(List<Oferta> availableSellOffers, OrdenDeCompra ordenCompra) throws InvalidQueryException {
		Oferta bestOffer = null;
		List<OrdenDeCompra> orden = new ArrayList<OrdenDeCompra>();
		BigDecimal sumaTotalSinOferta = ProductUtils.sumarTotalProductos(ordenCompra.getProductos());
		List<GroupSpecification<OrdenDeCompra>> offerQueries = QueryUtils.materializeQuerySellOffers(availableSellOffers);

		orden.add(ordenCompra);

		BigDecimal mejorPrecioVentaConOferta = new BigDecimal(sumaTotalSinOferta.toString());
		BigDecimal precioVentaConOferta = new BigDecimal(0);

		for (GroupSpecification<OrdenDeCompra> offer : offerQueries) {

			if (offer.isSatisfiedBy(orden)) {
				precioVentaConOferta = sumaTotalSinOferta.multiply(new BigDecimal(offer.getOfertaOriginal().getPorcentaje() / 100d));

				// Comparo contra el mejor precio
				if (mejorPrecioVentaConOferta.compareTo(precioVentaConOferta) > 0) {
					mejorPrecioVentaConOferta = precioVentaConOferta;
					bestOffer = offer.getOfertaOriginal();
				}
			}

		}

		return bestOffer;
	}

	public static List<Oferta> calculateBestOffersOrdering(List<Oferta> availableOffers, OrdenDeCompra ordenCompra) throws InvalidQueryException {
		List<GroupSpecification<Producto>> productQueries = QueryUtils.materializeQuery(availableOffers);

		BigDecimal sumaTotalSinPromocion = ProductUtils.sumarTotalProductos(ordenCompra.getProductos());

		// Permutar todos las ofertas
		Collection<List<GroupSpecification<Producto>>> ofertasPermutadas = Collections2.permutations(productQueries);

		BigDecimal mejorPrecio = sumaTotalSinPromocion;
		List<GroupSpecification<Producto>> mejorCombinacionPosible = null;
		for (List<GroupSpecification<Producto>> combinacionPosible : ofertasPermutadas) {

			BigDecimal sumaProductosTrasAnalisis = calcularPrecioTrasPromocion(combinacionPosible, ordenCompra);

			// Comparo contra el mejor precio
			if (mejorPrecio.compareTo(sumaProductosTrasAnalisis) > 0) {
				mejorPrecio = sumaProductosTrasAnalisis;
				mejorCombinacionPosible = combinacionPosible;
			}
		}

		if (mejorCombinacionPosible != null)
			return QueryUtils.fromMaterializedToOffer(mejorCombinacionPosible);
		else
			return null;
	}

	public static BigDecimal calcularPrecioTrasPromocion(List<GroupSpecification<Producto>> combinacionPosible, OrdenDeCompra ordenCompra) {

		BigDecimal sumaProductosQueCumplen = new BigDecimal(0);

		// Productos procesando
		List<Producto> productosProcesando = new ArrayList<Producto>(ordenCompra.getProductos());
		List<Producto> productosEnOferta = new ArrayList<Producto>();

		// Separar las ofertas por ocurrencia
		for (GroupSpecification<Producto> oferta : combinacionPosible) {
			if (oferta.isSatisfiedBy(productosProcesando)) {
				List<Producto> elementosQueCumplen = oferta.getElementosQueCumplen();
				sumaProductosQueCumplen = sumaProductosQueCumplen.add(aplicarDescuento(elementosQueCumplen, oferta));

				// productosProcesando.removeAll(elementosQueCumplen);
				productosProcesando = removeElements(productosProcesando, elementosQueCumplen);
				productosEnOferta.addAll(elementosQueCumplen);
			}
		}

		BigDecimal sumaProductosTrasAnalisis = sumaProductosQueCumplen.add(ProductUtils.sumarTotalProductos(productosProcesando));
		return sumaProductosTrasAnalisis;
	}

	private static List<Producto> removeElements(List<Producto> original, List<Producto> aEliminar) {
		List<Producto> listaNueva = new ArrayList<Producto>();
		boolean encontrado;

		for (Producto product : original) {
			encontrado = false;

			for (Producto productAEliminar : aEliminar) {
				if (product == productAEliminar) {// Es la misma instancia
					encontrado = true;
				}
			}

			if (!encontrado)
				listaNueva.add(product);

		}
		return listaNueva;
	}

	private static BigDecimal aplicarDescuento(List<Producto> elementosQueCumplen, GroupSpecification<Producto> oferta) {

		BigDecimal sumaParcial = new BigDecimal(0);

		for (Producto producto : elementosQueCumplen) {
			sumaParcial = sumaParcial.add(ProductUtils.precioProductoConDescuento(producto, oferta.getOfertaOriginal()));
		}

		return sumaParcial;
	}

	public static BigDecimal calcularSubtotal(List<Oferta> ofertasProductosAplicada, OrdenDeCompra ordenDeCompra) throws InvalidQueryException {
		return BestSell.calcularPrecioTrasPromocion(QueryUtils.materializeQuery(ofertasProductosAplicada), ordenDeCompra);
	}

	public static BigDecimal calcularTotal(OrdenDeCompra ordenDeCompra, List<Oferta> ofertasProductosAplicada, List<Oferta> ofertaVentaAplicada)
			throws InvalidQueryException {
		BigDecimal precioTrasPromoProductos = BestSell.calcularPrecioTrasPromocion(QueryUtils.materializeQuery(ofertasProductosAplicada),
				ordenDeCompra);
		BigDecimal precioFinal = precioTrasPromoProductos;
		// TODO esto esta mal. Podes aplicar mas de una oferta a la venta,sino
		// para que es list?
		if (ofertaVentaAplicada != null && ofertaVentaAplicada.size() > 0) {
			BigDecimal porcentaje = new BigDecimal(ofertaVentaAplicada.get(0).getPorcentaje());
			porcentaje = porcentaje.divide(new BigDecimal("100"));
			precioFinal = precioTrasPromoProductos.multiply(porcentaje);
		}
		return precioFinal;
	}

	public static BigDecimal calculateTotalCupons(List<Cupon> discountCuponsList) {
		BigDecimal totalResult = new BigDecimal("0");
		for (Cupon cupon : discountCuponsList) {
			totalResult = totalResult.add(cupon.getMonto());
		}
		return totalResult;
	}

	public static BigDecimal applyCuponsDiscount(BigDecimal subtotal, BigDecimal totalCupons) {
		BigDecimal totalResult = subtotal;
		BigDecimal maxDiscountAllowed = new BigDecimal(MAX_DISCOUNT_PERCENT).multiply(subtotal).divide(new BigDecimal("100"));
		if (totalCupons.compareTo(maxDiscountAllowed) <= 0) {
			totalResult = totalResult.subtract(totalCupons);
		} else {
			totalResult = totalResult.subtract(maxDiscountAllowed);
		}
		return totalResult;
	}

}
